﻿using System;
using System.Reflection;
using System.Web.Management;
using System.Diagnostics;

namespace OpenLightGroup.Common.ApplicationServices
{
    /// <summary>
    /// Class used to log debug information, general event and errors to the ASP.Net 
    /// health monitoring API.
    /// </summary>
    public sealed class EventLogger
    {
        #region General
        /// <summary>
        /// Logs the specified message.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <typeparam name="T">The GeneralEvent type to be logged.</typeparam>
        public static void Log<T>(string message) where T : GeneralEvent
        {
            Log<T>(message, null, 0);
        }

        public static void Log<T>(string message,
                                   object eventSource,
                                   int eventDetailCode) where T : GeneralEvent
        {
            var eventToRaise = Create<T>(message, eventSource, eventDetailCode);

            if (eventToRaise != null)
            {
                eventToRaise.Raise();
                TraceEvent(eventToRaise);
            }
        }

        public static void LogWarning(string message)
        {
            Log<WarningEvent>(message, null, 0);
        }

        public static void LogInformation(string message)
        {
            Log<InformationEvent>(message, null, 0);
        }

        #endregion

        #region Debug

        /// <summary>
        /// Sends the specified message to the health monitor API. 
        /// Will only be executed if compiled in Debug mode.
        /// </summary>
        /// <param name="message">The message.</param>
        public static void Debug(string message)
        {

            Debug<DebugEvent>(message);

        }

        /// <summary>
        /// Sends the specified message to the health monitor API. 
        /// Will only be executed if compiled in Debug mode.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <typeparam name="T">The type of DebugEvent to log.</typeparam>
        public static void Debug<T>(string message) where T : DebugEvent
        {

            Debug<T>(message, null, 0);

        }

        /// <summary>
        /// Sends the specified message to the health monitor API. 
        /// Will only be executed if compiled in Debug mode.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="eventSource">The event source.</param>
        /// <param name="eventDetailCode">The event detail code.</param>
        /// <typeparam name="T">The type of DebugEvent to log.</typeparam>
        public static void Debug<T>(string message,
                                     object eventSource,
                                     int eventDetailCode) where T : DebugEvent
        {

            var eventToRaise = CreateDebugEvent
            <DebugEvent>(message, eventSource, eventDetailCode);

            if (eventToRaise != null)
            {
                eventToRaise.Raise();

                TraceEvent(eventToRaise);
            }

        }

        private static void TraceEvent(WebBaseEvent eventToRaise)
        {
            Trace.WriteLine(eventToRaise.ToString());
            Console.WriteLine(eventToRaise.ToString());
        }

        #endregion

        #region Errors

        public static void LogError(Exception e)
        {
            LogError<ErrorEvent>(e.Message, e.Source, 0, e);
        }

        public static void LogError<T>(Exception e)
        where T : ErrorEvent
        {
            LogError<T>(e.Message, e.Source, 0, e);
        }

        public static void LogError<T>(string message,
                                        object eventSource,
                                        int eventDetailCode,
                                        Exception e)
        where T : ErrorEvent
        {
            var eventToRaise = Create<T>(message, eventSource, eventDetailCode, e);

            if (eventToRaise != null)
            {
                eventToRaise.Raise();
                TraceEvent(eventToRaise);
            }
        }

        #endregion

        #region Client

        internal static void LogClientDebugEvent(EventLogRequest request)
        {
            try
            {
                var eventToRaise = new ClientDebugEvent(request.Message,
                                                            request.EventSource, 700000, request.EventDetailCode, request);


                if (eventToRaise != null)
                {
                    eventToRaise.Raise();
                    TraceEvent(eventToRaise);
                }
            }
            catch (Exception ex)
            {
                //Todo log a custom error about failed to log event :P Do this for each of these methods... client event factory methods
                LogError(new InvalidOperationException("There was an error logging a client debug event. See innerException for details: ", ex));
            }

        }

        internal static void LogClientErrorEvent(EventLogRequest request)
        {
            //ToDo handle EventTime and EventTimeUTC correctly.
            Exception ex = new Exception();

            try
            {
                ex = Activator.CreateInstance(Type.GetType(request.ExceptionType)) as Exception;

            }
            catch (Exception)
            {
                ex = new Exception("Could not create client exception type.");

                LogWarning(
                    "Could not create exception of type " + request.ExceptionType + " for client logging.");
            }

            var clientErr = new ClientErrorEvent(request.Message,
                                                  request.EventSource,
                                                  request.EventDetailCode,
                                                  ex,
                                                  request);


            clientErr.Raise();
            
            TraceEvent(clientErr);

        }

        internal static void LogClientEvent<T>(EventLogRequest request) where T : ClientEvent
        {
            ClientEvent eventToRaise = null;

            //object eventToRaise ;
            switch (request.EventType)
            {
                case ClientEventTypes.Debug:

                    LogClientDebugEvent(request);


                    break;

                case ClientEventTypes.Information:


                    eventToRaise = new ClientInformationEvent(request.Message,
                                                                    request.EventSource,
                                                                    200000, request.EventDetailCode,
                                                                    request);
                    

                    break;

                case ClientEventTypes.Warning:

                    eventToRaise = new ClientWarningEvent(request.Message,
                                                                   request.EventSource,
                                                                  300000, request.EventDetailCode, request);
                    break;

                case ClientEventTypes.Error:
                    LogClientErrorEvent(request);

                    //eventToRaise = new ClientErrorEvent(request.Message,
                    //                                        request.EventSource,
                    //                                        request.EventDetailCode, null, request);

                    //error.EventLogRequest = request;

                    //if (error != null)
                    //    error.Raise();

                    break;

                default:
                    break;
            }





            if (eventToRaise != null)
            {
                eventToRaise.Raise();
            }


        }

        #endregion



        #region Create

        private static T InvokeInstance<T>(Type[] paramTypes, object[] paramList)
        {
            var ctr = typeof(T).GetConstructor(
                BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance,
                null,
                paramTypes,
                null);

            if (ctr != null)
            {
                return (T)ctr.Invoke(paramList);
            }
            else
                return default(T);
        }

        protected static T Create<T>(
            string message,
            object eventSource,
            int eventDetailCode)
        where T : GeneralEvent
        {
            Type[] paramTypes = new Type[]
                                {
                                    typeof(string),
                                    typeof(object),
                                    typeof(int)
                                };

            object[] paramList = new object[]
                                 {
                                     message,
                                     eventSource,
                                     eventDetailCode
                                 };

            return InvokeInstance<T>(paramTypes, paramList);
        }

        protected static T CreateDebugEvent<T>(
            string message,
            object source,
            int detailCode)
        where T : DebugEvent
        {
            Type[] paramTypes = new Type[]
                                {
                                    typeof(string),
                                    typeof(object),
                                    typeof(int)
                                };

            object[] paramList = new object[] 
                                 { 
                                     message,
                                     source,
                                     detailCode,
                                 };

            return InvokeInstance<T>(paramTypes, paramList);
        }

        protected static T Create<T>(Exception ex)
        where T : ErrorEvent
        {
            return Create<T>(ex.Message, ex.Source, 0, ex);
        }

        protected static T Create<T>(
            string message,
            object source,
            int detailCode,
            Exception ex)
        where T : ErrorEvent
        {
            Type[] paramTypes = new Type[]
                                {
                                    typeof(string),
                                    typeof(object),
                                    typeof(int),
                                    typeof(Exception)
                                };

            object[] paramList = new object[] 
                                 { 
                                     message,
                                     source,
                                     detailCode,
                                     ex 
                                 };

            return InvokeInstance<T>(paramTypes, paramList);
        }
        #endregion
    }
}